// SnapEngine.h
//
#ifndef SNAPENGINE_H
#define SNAPENGINE_H

#include <list>
#include "IDSReal2D.h"
#include "TimedBlock.h"


namespace IDS{
namespace IDEAS{
namespace OpenGLView{

template <typename ObjectType>
class SnapEngine2D
{
private:
	struct SnapPoint2D
	{
		IDSReal2D chart;
		IDSReal2D lonlat;
		bool intersection;
		
		std::list<ObjectType*> objs;
	};

public:
	typedef std::list< SnapPoint2D > SnapPointList;
	typedef typename SnapPointList::iterator SnapPointListIter;

	//class SnapNode : public Utils::Node<4>
	//{
	//private:
	//	IDSReal3D p;
	//
	//public:
	//	SnapNode() : Node(-1) {}
	//	virtual ~SnapNode() {}

	//	void set(const IDSReal3D &pt) {p = pt;}

	//	double x()	{return p[0];}
	//	double y()	{return p[1];}

	//	int getQuadrantOf(const IDSReal3D &q);
	//};

	//SnapNode *root;

	//std::list< std::pair<IDSReal3D,IDSReal3D> > points;
private:
	SnapPointList		points;
	SnapPointListIter	lastIter;

public:
	// ------------------------------------------------------------------------------------------------
	SnapEngine2D()				{ reset(); }
	virtual ~SnapEngine2D()		{ reset(); }
	
	// ------------------------------------------------------------------------------------------------
	void reset()
	{
		points.clear();
		lastIter = points.end();
	}
	
	// ------------------------------------------------------------------------------------------------
	void insertPoint(ObjectType *obj,const IDSReal2D &chart, const IDSReal2D &geo)
	{
		// look if current point is already present
		SnapPoint2D p;
		p.chart        = chart;
		p.lonlat       = geo;
		p.intersection = false;
		p.objs.push_back(obj);
		points.push_back(p);

		/*
		SnapPointListIter it = findPoint(chart,IDSMath::TOLERANCE);
		
		// add a new point
		if( it == points.end() )
		{
			SnapPoint2D p;
			p.chart        = chart;
			p.lonlat       = geo;
			p.intersection = false;
			p.objs.push_back(obj);
			points.push_back(p);
		}
		else
		{
			//if( it->intersection = isIntersection;
			it->objs.push_back(obj);
		}
		*/
#if _DEBUG
		//printf("SnapPoints: %d\n",points.size());
#endif
	}

	// ------------------------------------------------------------------------------------------------
	void insertPoint(ObjectType *objA,ObjectType *objB,const IDSReal2D &chart, const IDSReal2D &geo)
	{
		// look if current point is already present
		SnapPointListIter it = findPoint(chart,IDSMath::TOLERANCE);
		
		// add a new point
		if( it == points.end() )
		{
			SnapPoint2D p;
			p.chart        = chart;
			p.lonlat       = geo;
			p.intersection = true;
			p.objs.push_back(objA);
			p.objs.push_back(objB);
			points.push_back(p);
		}
		else
		{
			//if( it->intersection );
				it->objs.push_back(objA);
		}
#if _DEBUG
		//printf("SnapPoints: %d\n",points.size());
#endif
	}

	// ------------------------------------------------------------------------------------------------
	void removePoint(ObjectType *obj)
	{
		// for each snap point
		for(SnapPointListIter it = points.begin();
			it != points.end();
			/**/)
		{
			std::list<SnapPoint2D>::iterator pointIter = it++;

			// search if current object share this snap point
			for(std::list<ObjectType*>::iterator objIter =  pointIter->objs.begin();
				objIter != pointIter->objs.end();
				/**/)
			{
				std::list<ObjectType*>::iterator jt = objIter++;

				// current object found
				if( (*jt) == obj )
					pointIter->objs.erase(jt);
			}

			// remove the point either if shared by no objects
			// or there is no more intersection
			if( pointIter->objs.size() == 0 || (pointIter->objs.size() == 1 && pointIter->intersection))
				points.erase(pointIter);
		}

#if _DEBUG
		//printf("SnapPoints: %d\n",points.size());
#endif
	}

	// ------------------------------------------------------------------------------------------------
	void removePoint(const IDSReal2D &chart)
	{
		for(SnapPointListIter it = points.begin();
			it != points.end();
			/**/)
		{
			SnapPointListIter jt = it++;
			if( jt->chart.equals(chart,IDSMath::TOLERANCE) )
			{
				points.erase(jt);
				break;
			}
		}	
	}

	// ------------------------------------------------------------------------------------------------
	SnapPointListIter findPoint(const IDSReal2D &q, double accuracy)
	{
		//TimedBlock t("  Find Point",f,true ,false);

		double sqrDst = accuracy * accuracy * 2;

		SnapPointListIter jt = points.end();

		for(SnapPointListIter it = points.begin();
			it != points.end();
			++it)
		{
			if( it->chart.equals(q,accuracy) )
			{
				double d = (it->chart-q).sqrmod();
				if( d < sqrDst)
				{
					sqrDst = d;
					jt    = it;
				}
			}
		}

		// cache last result
		lastIter = jt;

		return jt;	
	}
	
	// ------------------------------------------------------------------------------------------------
	SnapPointList &pointList() {return points;}
	
	// ------------------------------------------------------------------------------------------------
	std::list<ObjectType*> snappedObjects()
	{
		std::list<ObjectType*> curr;
		
		if(lastIter != points.end() )
			curr = lastIter->objs;

		return curr;
	}

};

}}}

#endif